/* 
Copyright Paul James Mutton, 2001-2007, http://www.jibble.org/

This file is part of PircBot.

This software is dual-licensed, allowing you to choose between the GNU
General Public License (GPL) and the www.jibble.org Commercial License.
Since the GPL may be too restrictive for use in a proprietary application,
a commercial license is also provided. Full license information can be
found at http://www.jibble.org/licenses/

 */

package org.jibble.pircbot;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.net.SocketFactory;

/**
 * PircBot is a Java framework for writing IRC bots quickly and easily.
 * <p>
 * It provides an event-driven architecture to handle common IRC events, flood
 * protection, DCC support, ident support, and more. The comprehensive logfile
 * format is suitable for use with pisg to generate channel statistics.
 * <p>
 * Methods of the PircBot class can be called to send events to the IRC server
 * that it connects to. For example, calling the sendMessage method will send a
 * message to a channel or user on the IRC server. Multiple servers can be
 * supported using multiple instances of PircBot.
 * <p>
 * To perform an action when the PircBot receives a normal message from the IRC
 * server, you would override the onMessage method defined in the PircBot class.
 * All on<i>XYZ</i> methods in the PircBot class are automatically called when
 * the event <i>XYZ</i> happens, so you would override these if you wish to do
 * something when it does happen.
 * <p>
 * Some event methods, such as onPing, should only really perform a specific
 * function (i.e. respond to a PING from the server). For your convenience, such
 * methods are already correctly implemented in the PircBot and should not
 * normally need to be overridden. Please read the full documentation for each
 * method to see which ones are already implemented by the PircBot class.
 * <p>
 * Please visit the PircBot homepage at <a
 * href="http://www.jibble.org/pircbot.php"
 * >http://www.jibble.org/pircbot.php</a> for full revision history, a beginners
 * guide to creating your first PircBot and a list of some existing Java IRC
 * bots and clients that use the PircBot framework.
 * 
 * @author Paul James Mutton, <a
 *         href="http://www.jibble.org/">http://www.jibble.org/</a>, modified by
 *         Alexander Boyd for use with JZBot (http://jzbot.googlecode.com)
 * @version 1.4.6 (Build time: Wed Apr 11 19:20:59 2007)
 */
public abstract class PircBot implements ReplyConstants {

	public static enum UserMode {
		o("@"), v("+"), h("%"), q("~"), a("&");
		private String symbol;

		private UserMode(String symbol) {
			this.symbol = symbol;
		}

		public String getSymbol()

		{
			return this.symbol;
		}
	}

	/*
	 * private static final int OP_ADD = 1; private static final int OP_REMOVE =
	 * 2; private static final int VOICE_ADD = 3; private static final int
	 * VOICE_REMOVE = 4; private static final int HALFOP_ADD = 5; private static
	 * final int HALFOP_REMOVE = 6; private static final int FOUNDER_ADD = 7;
	 * private static final int FOUNDER_REMOVE = 8;
	 */

	public static final String VERSION = "Epic";

	public static String modifyPrefix(String current, String n, boolean add) {
		if (add) {
			if (current.contains(n)) {
				return current;
			}
			return n + current;
		} else {
			return current.replace(n, "");
		}
	}

	private SocketFactory _socketFactory = null;

	private static HashMap<String, UserMode> reverseUserModeMap = new HashMap<String, UserMode>();

	static {
		for (UserMode mode : UserMode.values()) {
			reverseUserModeMap.put(mode.getSymbol(), mode);
		}
	}

	// Connection stuff.
	private InputThread _inputThread = null;

	private OutputThread _outputThread = null;

	private String _charset = null;

	private InetAddress _inetAddress = null;

	// Details about the last server that we connected to.
	private String _server = null;

	private int _port = -1;

	private String _password = null;

	// Outgoing message stuff.
	private Queue _outQueue = new Queue();

	private long _messageDelay = 1000;

	// A Hashtable of channels that points to a selfreferential Hashtable of
	// User objects (used to remember which users are in which channels).
	private Map<String, Set<IrcUser>> _channels = new HashMap<String, Set<IrcUser>>();

	// A Hashtable to temporarily store channel topics when we join them
	// until we find out who set that topic.
	private Map<String, String> _topics = new HashMap<String, String>();

	// DccManager to process and handle all DCC events.
	private DccManager _dccManager = new DccManager(this);

	private int[] _dccPorts = null;

	private InetAddress _dccInetAddress = null;

	// Default settings for the PircBot.
	private boolean _autoNickChange = false;

	private boolean _verbose = false;

	private String _name = "PircBot";

	private String _nick = _name;

	private String _login = "RPGBot";

	private String _version = "RPGBOT v" + VERSION;

	private String _finger = "You ought to be arrested for fingering a bot!";

	private String _channelPrefixes = "#&+!";

	/**
	 * Constructs a PircBot with the default settings. Your own constructors in
	 * classes which extend the PircBot abstract class should be responsible for
	 * changing the default settings if required.
	 */
	public PircBot() {
	}

	/**
	 * Add a user to the specified channel in our memory. Overwrite the existing
	 * entry if it exists.
	 */
	protected void addUser(String channel, IrcUser user) {
		channel = channel.toLowerCase();
		Set<IrcUser> users = _channels.get(channel);
		if (users == null) {
			users = new HashSet<IrcUser>();
			_channels.put(channel, users);
		}
		users.add(user);
	}

	/**
	 * Bans a user from a channel. An example of a valid hostmask is
	 * "*!*compu@*.18hp.net". This may be used in conjunction with the kick
	 * method to permanently remove a user from a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel to ban the user from.
	 * @param hostmask
	 *            A hostmask representing the user we're banning.
	 */
	public void ban(String channel, String hostmask) {
		this.sendRawLine("MODE " + channel + " +b " + hostmask);
	}

	/**
	 * Attempt to change the current nick (nickname) of the bot when it is
	 * connected to an IRC server. After confirmation of a successful nick
	 * change, the getNick method will return the new nick.
	 * 
	 * @param newNick
	 *            The new nick to use.
	 */
	public void changeNick(String newNick) {
		this.sendRawLine("NICK " + newNick);
	}

	/**
	 * Attempt to connect to the specified IRC server. The onConnect method is
	 * called upon success.
	 * 
	 * @param hostname
	 *            The hostname of the server to connect to.
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void connect(String hostname) throws IOException,
			IrcException, NickAlreadyInUseException {
		this.connect(hostname, 6667, null, getSocketFactory());
	}

	/**
	 * Attempt to connect to the specified IRC server and port number. The
	 * onConnect method is called upon success.
	 * 
	 * @param hostname
	 *            The hostname of the server to connect to.
	 * @param port
	 *            The port number to connect to on the server.
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void connect(String hostname, int port)
			throws IOException, IrcException, NickAlreadyInUseException {
		this.connect(hostname, port, null, getSocketFactory());
	}

	/**
	 * Attempt to connect to the specified IRC server and port number. The
	 * onConnect method is called upon success.
	 * 
	 * @param hostname
	 *            The hostname of the server to connect to.
	 * @param port
	 *            The port number to connect to on the server.
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void connect(String hostname, int port,
			SocketFactory socketFactory) throws IOException, IrcException,
			NickAlreadyInUseException {
		this.connect(hostname, port, null, socketFactory);
	}

	/**
	 * Attempt to connect to the specified IRC server using the supplied
	 * password. The onConnect method is called upon success.
	 * 
	 * @param hostname
	 *            The hostname of the server to connect to.
	 * @param port
	 *            The port number to connect to on the server.
	 * @param password
	 *            The password to use to join the server.
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void connect(String hostname, int port,
			String password) throws IOException, IrcException,
			NickAlreadyInUseException {

		_server = hostname;
		_port = port;
		_password = password;

		if (isConnected()) {
			throw new IOException(
					"The PircBot is already connected to an IRC server.  Disconnect first.");
		}

		// Don't clear the outqueue - there might be something important in it!

		// Clear everything we may have know about channels.
		this.removeAllChannels();

		// Connect to the server.
		Socket socket = new Socket();
		InetAddress[] netAddresses = InetAddress.getAllByName(hostname);
		if (netAddresses.length == 0) {
			throw new RuntimeException("No addresses for host " + hostname);
		}
		InetAddress netAddress = netAddresses[(int) (Math.random() * netAddresses.length)];
		System.out.println("Connecting to server "
				+ netAddress.getHostAddress());
		try {
			System.out.println("Canonical host: "
					+ netAddress.getCanonicalHostName());
		} catch (Exception e) {
		}
		InetSocketAddress address = new InetSocketAddress(netAddress, port);
		socket.connect(address, 15 * 1000);
		this.log("*** Connected to server.");

		_inetAddress = socket.getLocalAddress();

		InputStreamReader inputStreamReader = null;
		OutputStreamWriter outputStreamWriter = null;
		if (getEncoding() != null) {
			// Assume the specified encoding is valid for this JVM.
			inputStreamReader = new InputStreamReader(socket.getInputStream(),
					getEncoding());
			outputStreamWriter = new OutputStreamWriter(
					socket.getOutputStream(), getEncoding());
		} else {
			// Otherwise, just use the JVM's default encoding.
			inputStreamReader = new InputStreamReader(socket.getInputStream());
			outputStreamWriter = new OutputStreamWriter(
					socket.getOutputStream());
		}

		BufferedReader breader = new BufferedReader(inputStreamReader);
		BufferedWriter bwriter = new BufferedWriter(outputStreamWriter);

		// Attempt to join the server.
		if (password != null && !password.equals("")) {
			OutputThread.sendRawLine(this, bwriter, "PASS " + password);
		}
		String nick = this.getName();
		OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
		OutputThread.sendRawLine(this, bwriter, "USER " + this.getLogin()
				+ " 8 * :" + this.getVersion());

		_inputThread = new InputThread(this, socket, breader, bwriter);

		// Read stuff back from the server to see if we connected.
		String line = null;
		int tries = 1;
		while ((line = breader.readLine()) != null) {

			this.handleLine(line);

			int firstSpace = line.indexOf(" ");
			int secondSpace = line.indexOf(" ", firstSpace + 1);
			if (secondSpace >= 0) {
				String code = line.substring(firstSpace + 1, secondSpace);

				if (code.equals("004")) {
					// We're connected to the server.
					break;
				} else if (code.equals("433")) {
					if (_autoNickChange) {
						tries++;
						nick = getName() + tries;
						OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
					} else {
						socket.close();
						_inputThread = null;
						throw new NickAlreadyInUseException(line);
					}
				} else if (code.startsWith("5") || code.startsWith("4")) {
					if (code.equals("439")) {
						// 439 seems to be sent by a lot of servers as a warning
						// code
						// about
						// spambot protection, but it doesn't appear to be a
						// fatal error,
						// so we'll
						// ignore it here.
						System.out.println("Code 439 received by " + _server
								+ ": " + line);
					} else {
						socket.close();
						_inputThread = null;
						throw new IrcException(
								"Could not log into the IRC server: " + line);
					}
				}
			}
			this.setNick(nick);

		}

		this.log("*** Logged onto server.");

		// This makes the socket timeout on read operations after 5 minutes.
		// Maybe in some future version I will let the user change this at
		// runtime.
		socket.setSoTimeout(5 * 60 * 1000);

		// Now start the InputThread to read all other lines from the server.
		_inputThread.start();

		// Now start the outputThread that will be used to send all messages.
		if (_outputThread == null) {
			_outputThread = new OutputThread(this, _outQueue);
			_outputThread.start();
		}

		this.onConnect();

	}

	/**
	 * Attempt to connect to the specified IRC server using the supplied
	 * password. The onConnect method is called upon success.
	 * 
	 * @param hostname
	 *            The hostname of the server to connect to.
	 * @param port
	 *            The port number to connect to on the server.
	 * @param password
	 *            The password to use to join the server.
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void connect(String hostname, int port,
			String password, SocketFactory socketFactory) throws IOException,
			IrcException, NickAlreadyInUseException {

		_server = hostname;
		_port = port;
		_password = password;

		if (isConnected()) {
			throw new IOException(
					"The PircBot is already connected to an IRC server.  Disconnect first.");
		}

		// Don't clear the outqueue - there might be something important in it!

		// Clear everything we may have know about channels.
		this.removeAllChannels();

		// Connect to the server.
		// Socket socket = new Socket(hostname, port);
		Socket socket;
		if (socketFactory == null) {
			socket = new Socket(hostname, port);
		} else {
			socket = socketFactory.createSocket(hostname, port);
		}
		this.log("*** Connected to server.");

		_inetAddress = socket.getLocalAddress();

		InputStreamReader inputStreamReader = null;
		OutputStreamWriter outputStreamWriter = null;
		if (getEncoding() != null) {
			// Assume the specified encoding is valid for this JVM.
			inputStreamReader = new InputStreamReader(socket.getInputStream(),
					getEncoding());
			outputStreamWriter = new OutputStreamWriter(
					socket.getOutputStream(), getEncoding());
		} else {
			// Otherwise, just use the JVM's default encoding.
			inputStreamReader = new InputStreamReader(socket.getInputStream());
			outputStreamWriter = new OutputStreamWriter(
					socket.getOutputStream());
		}

		BufferedReader breader = new BufferedReader(inputStreamReader);
		BufferedWriter bwriter = new BufferedWriter(outputStreamWriter);

		// Attempt to join the server.
		if (password != null && !password.equals("")) {
			OutputThread.sendRawLine(this, bwriter, "PASS " + password);
		}
		String nick = this.getName();
		OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
		OutputThread.sendRawLine(this, bwriter, "USER " + this.getLogin()
				+ " 8 * :" + this.getVersion());

		_inputThread = new InputThread(this, socket, breader, bwriter);

		// Read stuff back from the server to see if we connected.
		String line = null;
		int tries = 1;
		while ((line = breader.readLine()) != null) {

			this.handleLine(line);

			int firstSpace = line.indexOf(" ");
			int secondSpace = line.indexOf(" ", firstSpace + 1);
			if (secondSpace >= 0) {
				String code = line.substring(firstSpace + 1, secondSpace);

				if (code.equals("004")) {
					// We're connected to the server.
					break;
				} else if (code.equals("433")) {
					if (_autoNickChange) {
						tries++;
						nick = getName() + tries;
						OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
					} else {
						socket.close();
						_inputThread = null;
						throw new NickAlreadyInUseException(line);
					}
				} else if (code.startsWith("5") || code.startsWith("4")) {
					socket.close();
					_inputThread = null;
					throw new IrcException(
							"Could not log into the IRC server: " + line);
				}
			}
			this.setNick(nick);

		}

		this.log("*** Logged onto server.");

		// This makes the socket timeout on read operations after 5 minutes.
		// Maybe in some future version I will let the user change this at
		// runtime.
		socket.setSoTimeout(5 * 60 * 1000);

		// Now start the InputThread to read all other lines from the server.
		_inputThread.start();

		// Now start the outputThread that will be used to send all messages.
		if (_outputThread == null) {
			_outputThread = new OutputThread(this, _outQueue);
			_outputThread.start();
		}

		this.onConnect();

	}

	/**
	 * Attempts to accept a DCC CHAT request by a client. Please use the
	 * onIncomingChatRequest method to receive files.
	 * 
	 * @deprecated As of PircBot 1.2.0, use
	 *             {@link #onIncomingChatRequest(DccChat)}
	 */
	protected final DccChat dccAcceptChatRequest(String sourceNick,
			long address, int port) {
		throw new RuntimeException(
				"dccAcceptChatRequest is deprecated, please use onIncomingChatRequest");
	}

	/**
	 * Receives a file that is being sent to us by a DCC SEND request. Please
	 * use the onIncomingFileTransfer method to receive files.
	 * 
	 * @deprecated As of PircBot 1.2.0, use
	 *             {@link #onIncomingFileTransfer(DccFileTransfer)}
	 */
	protected final void dccReceiveFile(File file, long address, int port,
			int size) {
		throw new RuntimeException(
				"dccReceiveFile is deprecated, please use sendFile");
	}

	/**
	 * Attempts to establish a DCC CHAT session with a client. This method
	 * issues the connection request to the client and then waits for the client
	 * to respond. If the connection is successfully made, then a DccChat object
	 * is returned by this method. If the connection is not made within the time
	 * limit specified by the timeout value, then null is returned.
	 * <p>
	 * It is <b>strongly recommended</b> that you call this method within a new
	 * Thread, as it may take a long time to return.
	 * <p>
	 * This method may not be overridden.
	 * 
	 * @since PircBot 0.9.8
	 * 
	 * @param nick
	 *            The nick of the user we are trying to establish a chat with.
	 * @param timeout
	 *            The number of milliseconds to wait for the recipient to accept
	 *            the chat connection (we recommend about 120000).
	 * 
	 * @return a DccChat object that can be used to send and recieve lines of
	 *         text. Returns <b>null</b> if the connection could not be made.
	 * 
	 * @see DccChat
	 */
	public DccChat dccSendChatRequest(String nick, int timeout) {
		DccChat chat = null;
		try {
			ServerSocket ss = null;

			int[] ports = getDccPorts();
			if (ports == null) {
				// Use any free port.
				ss = new ServerSocket(0);
			} else {
				for (int i = 0; i < ports.length; i++) {
					try {
						ss = new ServerSocket(ports[i]);
						// Found a port number we could use.
						break;
					} catch (Exception e) {
						// Do nothing; go round and try another port.
					}
				}
				if (ss == null) {
					// No ports could be used.
					throw new IOException(
							"All ports returned by getDccPorts() are in use.");
				}
			}

			ss.setSoTimeout(timeout);
			int port = ss.getLocalPort();

			InetAddress inetAddress = getDccInetAddress();
			if (inetAddress == null) {
				inetAddress = getInetAddress();
			}
			byte[] ip = inetAddress.getAddress();
			long ipNum = ipToLong(ip);

			sendCTCPCommand(nick, "DCC CHAT chat " + ipNum + " " + port);

			// The client may now connect to us to chat.
			Socket socket = ss.accept();

			// Close the server socket now that we've finished with it.
			ss.close();

			chat = new DccChat(this, nick, socket);
		} catch (Exception e) {
			// Do nothing.
		}
		return chat;
	}

	/**
	 * Sends a file to another user. Resuming is supported. The other user must
	 * be able to connect directly to your bot to be able to receive the file.
	 * <p>
	 * You may throttle the speed of this file transfer by calling the
	 * setPacketDelay method on the DccFileTransfer that is returned.
	 * <p>
	 * This method may not be overridden.
	 * 
	 * @since 0.9c
	 * 
	 * @param file
	 *            The file to send.
	 * @param nick
	 *            The user to whom the file is to be sent.
	 * @param timeout
	 *            The number of milliseconds to wait for the recipient to
	 *            acccept the file (we recommend about 120000).
	 * 
	 * @return The DccFileTransfer that can be used to monitor this transfer.
	 * 
	 * @see DccFileTransfer
	 * 
	 */
	public DccFileTransfer dccSendFile(File file, String nick, int timeout) {
		DccFileTransfer transfer = new DccFileTransfer(this, _dccManager, file,
				nick, timeout);
		transfer.doSend(true);
		return transfer;
	}

	/**
	 * Removes hoperator privilidges from a user on a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're dehopping the user on.
	 * @param nick
	 *            The nick of the user we are dehopping.
	 */
	public void deHalfop(String channel, String nick) {
		this.setMode(channel, "-h " + nick);
	}

	/**
	 * Removes operator privilidges from a user on a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're deopping the user on.
	 * @param nick
	 *            The nick of the user we are deopping.
	 */
	public void deOp(String channel, String nick) {
		this.setMode(channel, "-o " + nick);
	}

	/**
	 * Removes voice privilidges from a user on a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're devoicing the user on.
	 * @param nick
	 *            The nick of the user we are devoicing.
	 */
	public void deVoice(String channel, String nick) {
		this.setMode(channel, "-v " + nick);
	}

	/**
	 * This method disconnects from the server cleanly by calling the
	 * quitServer() method. Providing the PircBot was connected to an IRC
	 * server, the onDisconnect() will be called as soon as the disconnection is
	 * made by the server.
	 * 
	 * @see #quitServer() quitServer
	 * @see #quitServer(String) quitServer
	 */
	public final synchronized void disconnect() {
		this.quitServer();
	}

	/**
	 * Disposes of all thread resources used by this PircBot. This may be useful
	 * when writing bots or clients that use multiple servers (and therefore
	 * multiple PircBot instances) or when integrating a PircBot with an
	 * existing program.
	 * <p>
	 * Each PircBot runs its own threads for dispatching messages from its
	 * outgoing message queue and receiving messages from the server. Calling
	 * dispose() ensures that these threads are stopped, thus freeing up system
	 * resources and allowing the PircBot object to be garbage collected if
	 * there are no other references to it.
	 * <p>
	 * Once a PircBot object has been disposed, it should not be used again.
	 * Attempting to use a PircBot that has been disposed may result in
	 * unpredictable behaviour.
	 * 
	 * @since 1.2.2
	 */
	public synchronized void dispose() {
		// System.out.println("disposing...");
		_outputThread.interrupt();
		_inputThread.dispose();
	}

	/**
	 * Returns true if and only if the object being compared is the exact same
	 * instance as this PircBot. This may be useful if you are writing a
	 * multiple server IRC bot that uses more than one instance of PircBot.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @return true if and only if Object o is a PircBot and equal to this.
	 */
	public boolean equals(Object o) {
		// This probably has the same effect as Object.equals, but that may
		// change...
		if (o instanceof PircBot) {
			PircBot other = (PircBot) o;
			return other == this;
		}
		return false;
	}

	/**
	 * Returns an array of all channels that we are in. Note that if you call
	 * this method immediately after joining a new channel, the new channel may
	 * not appear in this array as it is not possible to tell if the join was
	 * successful until a response is received from the IRC server.
	 * 
	 * @since PircBot 1.0.0
	 * 
	 * @return A String array containing the names of all channels that we are
	 *         in.
	 */
	public final String[] getChannels() {
		final String[] channels = new String[_channels.size()];
		return _channels.keySet().toArray(channels);
	}

	/**
	 * Returns the InetAddress used when sending DCC chat or file transfers. If
	 * this is null, the default InetAddress will be used.
	 * 
	 * @since PircBot 1.4.4
	 * 
	 * @return The current DCC InetAddress, or null if left as default.
	 */
	public InetAddress getDccInetAddress() {
		return _dccInetAddress;
	}

	/**
	 * Returns the set of port numbers to be used when sending a DCC chat or
	 * file transfer. This is useful when you are behind a firewall and need to
	 * set up port forwarding. The array of port numbers is traversed in
	 * sequence until a free port is found to listen on. A DCC tranfer will fail
	 * if all ports are already in use. If set to null, <i>any</i> free port
	 * number will be used.
	 * 
	 * @since PircBot 1.4.4
	 * 
	 * @return An array of port numbers that PircBot can use to send DCC
	 *         transfers, or null if any port is allowed.
	 */
	public int[] getDccPorts() {
		if (_dccPorts == null || _dccPorts.length == 0) {
			return null;
		}
		// Clone the array to prevent external modification.
		return (int[]) _dccPorts.clone();
	}

	/**
	 * Returns the encoding used to send and receive lines from the IRC server,
	 * or null if not set. Use the setEncoding method to change the encoding
	 * charset.
	 * 
	 * @since PircBot 1.0.4
	 * 
	 * @return The encoding used to send outgoing messages, or null if not set.
	 */
	public String getEncoding() {
		return _charset;
	}

	/**
	 * Gets the internal finger message of the PircBot.
	 * 
	 * @return The finger message of the PircBot.
	 */
	public final String getFinger() {
		return _finger;
	}

	/**
	 * Returns the InetAddress used by the PircBot. This can be used to find the
	 * I.P. address from which the PircBot is connected to a server.
	 * 
	 * @since PircBot 1.4.4
	 * 
	 * @return The current local InetAddress, or null if never connected.
	 */
	public InetAddress getInetAddress() {
		return _inetAddress;
	}

	/**
	 * Gets the internal login of the PircBot.
	 * 
	 * @return The login of the PircBot.
	 */
	public final String getLogin() {
		return _login;
	}

	/**
	 * Gets the maximum length of any line that is sent via the IRC protocol.
	 * The IRC RFC specifies that line lengths, including the trailing \r\n must
	 * not exceed 512 bytes. Hence, there is currently no option to change this
	 * value in PircBot. All lines greater than this length will be truncated
	 * before being sent to the IRC server.
	 * 
	 * @return The maximum line length (currently fixed at 512)
	 */
	public final int getMaxLineLength() {
		return InputThread.MAX_LINE_LENGTH;
	}

	/**
	 * Returns the number of milliseconds that will be used to separate
	 * consecutive messages to the server from the outgoing message queue.
	 * 
	 * @return Number of milliseconds.
	 */
	public final long getMessageDelay() {
		return _messageDelay;
	}

	/**
	 * Gets the name of the PircBot. This is the name that will be used as as a
	 * nick when we try to join servers.
	 * 
	 * @return The name of the PircBot.
	 */
	public String getName() {
		return _name;
	}

	/**
	 * Returns the current nick of the bot. Note that if you have just changed
	 * your nick, this method will still return the old nick until confirmation
	 * of the nick change is received from the server.
	 * <p>
	 * The nick returned by this method is maintained only by the PircBot class
	 * and is guaranteed to be correct in the context of the IRC server.
	 * 
	 * @since PircBot 1.0.0
	 * 
	 * @return The current nick of the bot.
	 */
	public String getNick() {
		return _nick;
	}

	/**
	 * Gets the number of lines currently waiting in the outgoing message Queue.
	 * If this returns 0, then the Queue is empty and any new message is likely
	 * to be sent to the IRC server immediately.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @return The number of lines in the outgoing message Queue.
	 */
	public final int getOutgoingQueueSize() {
		return _outQueue.size();
	}

	/**
	 * Returns the last password that we used when connecting to an IRC server.
	 * This does not imply that the connection attempt to the server was
	 * successful (we suggest you look at the onConnect method). A value of null
	 * is returned if the PircBot has never tried to connect to a server using a
	 * password.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @return The last password that we used when connecting to an IRC server.
	 *         Returns null if we have not previously connected using a
	 *         password.
	 */
	public String getPassword() {
		return _password;
	}

	/**
	 * Returns the port number of the last IRC server that the PircBot tried to
	 * connect to. This does not imply that the connection attempt to the server
	 * was successful (we suggest you look at the onConnect method). A value of
	 * -1 is returned if the PircBot has never tried to connect to a server.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @return The port number of the last IRC server we connected to. Returns
	 *         -1 if no connection attempts have ever been made.
	 */
	public final int getPort() {
		return _port;
	}

	/**
	 * Returns the name of the last IRC server the PircBot tried to connect to.
	 * This does not imply that the connection attempt to the server was
	 * successful (we suggest you look at the onConnect method). A value of null
	 * is returned if the PircBot has never tried to connect to a server.
	 * 
	 * @return The name of the last machine we tried to connect to. Returns null
	 *         if no connection attempts have ever been made.
	 */
	public final String getServer() {
		return _server;
	}

	/**
	 * Returns the last SocketFactory that we used to connect to an IRC server.
	 * This does not imply that the connection attempt to the server was
	 * successful (we suggest you look at the onConnect method). A value of null
	 * is returned if the PircBot has never tried to connect to a server using a
	 * SocketFactory.
	 * 
	 * @return The last SocketFactory that we used when connecting to an IRC
	 *         server. Returns null if we have not previously connected using a
	 *         SocketFactory.
	 */
	public final SocketFactory getSocketFactory() {
		return _socketFactory;
	}

	/**
	 * Returns an array of all users in the specified channel.
	 * <p>
	 * There are some important things to note about this method:-
	 * <ul>
	 * <li>This method may not return a full list of users if you call it before
	 * the complete nick list has arrived from the IRC server.</li>
	 * <li>If you wish to find out which users are in a channel as soon as you
	 * join it, then you should override the onUserList method instead of
	 * calling this method, as the onUserList method is only called as soon as
	 * the full user list has been received.</li>
	 * <li>This method will return immediately, as it does not require any
	 * interaction with the IRC server.</li>
	 * <li>The bot must be in a channel to be able to know which users are in
	 * it.</li>
	 * </ul>
	 * 
	 * @since PircBot 1.0.0
	 * 
	 * @param channel
	 *            The name of the channel to list.
	 * 
	 * @return An array of User objects. This array is empty if we are not in
	 *         the channel.
	 * 
	 * @see #onUserList(String,User[]) onUserList
	 */
	public final IrcUser[] getUsers(String channel) {
		channel = channel.toLowerCase();
		IrcUser[] userArray = new IrcUser[0];
		Set<IrcUser> users = _channels.get(channel);
		if (users != null) {
			userArray = new IrcUser[users.size()];
			userArray = users.toArray(userArray);
		}
		return userArray;
	}

	/**
	 * Gets the internal version of the PircBot.
	 * 
	 * @return The version of the PircBot.
	 */
	public final String getVersion() {
		return _version;
	}

	/**
	 * Grants hoperator privilidges to a user on a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're hopping the user on.
	 * @param nick
	 *            The nick of the user we are hopping.
	 */
	public void halfop(String channel, String nick) {
		this.setMode(channel, "+h " + nick);
	}

	/**
	 * This method handles events when any line of text arrives from the server,
	 * then calling the appropriate method in the PircBot. This method is
	 * protected and only called by the InputThread for this instance.
	 * <p>
	 * This method may not be overridden!
	 * 
	 * @param line
	 *            The raw line of text from the server.
	 */
	protected void handleLine(String line) {
		this.log(line);
		// Check for server pings.
		if (line.startsWith("PING ")) {
			// Respond to the ping and return immediately.
			this.onServerPing(line.substring(5));
			return;
		}

		String sourceNick = "";
		String sourceLogin = "";
		String sourceHostname = "";

		StringTokenizer tokenizer = new StringTokenizer(line);
		String senderInfo = tokenizer.nextToken();
		String command = tokenizer.nextToken();
		String target = null;

		int exclamation = senderInfo.indexOf("!");
		int at = senderInfo.indexOf("@");
		if (senderInfo.startsWith(":")) {
			if (exclamation > 0 && at > 0 && exclamation < at) {
				sourceNick = senderInfo.substring(1, exclamation);
				sourceLogin = senderInfo.substring(exclamation + 1, at);
				sourceHostname = senderInfo.substring(at + 1);
			} else {

				if (tokenizer.hasMoreTokens()) {
					String token = command;

					int code = -1;
					try {
						code = Integer.parseInt(token);
					} catch (NumberFormatException e) {
						// Keep the existing value.
					}

					if (code != -1) {
						String errorStr = token;
						String response = line
								.substring(
										line.indexOf(errorStr,
												senderInfo.length()) + 4,
										line.length());
						this.processServerResponse(code, response);
						// Return from the method.
						return;
					} else {
						// This is not a server response.
						// It must be a nick without login and hostname.
						// (or maybe a NOTICE or suchlike from the server)
						sourceNick = senderInfo;
						target = token;
					}
				} else {
					// We don't know what this line means.
					this.onUnknown(line);
					// Return from the method;
					return;
				}

			}
		}

		command = command.toUpperCase();
		if (sourceNick.startsWith(":")) {
			sourceNick = sourceNick.substring(1);
		}
		if (target == null) {
			target = tokenizer.nextToken();
		}
		if (target.startsWith(":")) {
			target = target.substring(1);
		}

		// Check for CTCP requests.
		if (command.equals("PRIVMSG") && line.indexOf(":\u0001") > 0
				&& line.endsWith("\u0001")) {
			String request = line.substring(line.indexOf(":\u0001") + 2,
					line.length() - 1);
			if (request.equals("VERSION")) {
				// VERSION request
				this.onVersion(sourceNick, sourceLogin, sourceHostname, target);
			} else if (request.startsWith("ACTION ")) {
				// ACTION request
				this.onAction(sourceNick, sourceLogin, sourceHostname, target,
						request.substring(7));
			} else if (request.startsWith("PING ")) {
				// PING request
				this.onPing(sourceNick, sourceLogin, sourceHostname, target,
						request.substring(5));
			} else if (request.equals("TIME")) {
				// TIME request
				this.onTime(sourceNick, sourceLogin, sourceHostname, target);
			} else if (request.equals("FINGER")) {
				// FINGER request
				this.onFinger(sourceNick, sourceLogin, sourceHostname, target);
			} else if ((tokenizer = new StringTokenizer(request)).countTokens() >= 5
					&& tokenizer.nextToken().equals("DCC")) {
				// This is a DCC request.
				boolean success = _dccManager.processRequest(sourceNick,
						sourceLogin, sourceHostname, request);
				if (!success) {
					// The DccManager didn't know what to do with the line.
					this.onUnknown(line);
				}
			} else {
				// An unknown CTCP message - ignore it.
				this.onUnknown(line);
			}
		} else if (command.equals("PRIVMSG")
				&& _channelPrefixes.indexOf(target.charAt(0)) >= 0) {
			// This is a normal message to a channel.
			this.onMessage(target, sourceNick, sourceLogin, sourceHostname,
					line.substring(line.indexOf(" :") + 2));
		} else if (command.equals("PRIVMSG")) {
			// This is a private message to us.
			this.onPrivateMessage(sourceNick, sourceLogin, sourceHostname,
					line.substring(line.indexOf(" :") + 2));
		} else if (command.equals("JOIN")) {
			// Someone is joining a channel.
			String channel = target;
			this.addUser(channel, new IrcUser("", sourceNick));
			this.onJoin(channel, sourceNick, sourceLogin, sourceHostname);
		} else if (command.equals("PART")) {
			// Someone is parting from a channel.
			this.removeUser(target, sourceNick);
			if (sourceNick.equals(this.getNick())) {
				this.removeChannel(target);
			}
			this.onPart(target, sourceNick, sourceLogin, sourceHostname);
		} else if (command.equals("NICK")) {
			// Somebody is changing their nick.
			String newNick = target;
			this.renameUser(sourceNick, newNick);
			if (sourceNick.equals(this.getNick())) {
				// Update our nick if it was us that changed nick.
				this.setNick(newNick);
			}
			this.onNickChange(sourceNick, sourceLogin, sourceHostname, newNick);
		} else if (command.equals("NOTICE")) {
			// Someone is sending a notice.
			this.onNotice(sourceNick, sourceLogin, sourceHostname, target,
					line.substring(line.indexOf(" :") + 2));
		} else if (command.equals("QUIT")) {
			// Someone has quit from the IRC server.
			this.onBeforeQuit(sourceNick, sourceLogin, sourceHostname,
					line.substring(line.indexOf(" :") + 2));
			if (sourceNick.equals(this.getNick())) {
				this.removeAllChannels();
			} else {
				this.removeUser(sourceNick);
			}
			this.onQuit(sourceNick, sourceLogin, sourceHostname,
					line.substring(line.indexOf(" :") + 2));
		} else if (command.equals("KICK")) {
			// Somebody has been kicked from a channel.
			String recipient = tokenizer.nextToken();
			if (recipient.equals(this.getNick())) {
				this.removeChannel(target);
			}
			this.removeUser(target, recipient);
			this.onKick(target, sourceNick, sourceLogin, sourceHostname,
					recipient, line.substring(line.indexOf(" :") + 2));
		} else if (command.equals("MODE")) {
			// Somebody is changing the mode on a channel or user.
			String mode = line.substring(line.indexOf(target, 2)
					+ target.length() + 1);
			if (mode.startsWith(":")) {
				mode = mode.substring(1);
			}
			this.processMode(target, sourceNick, sourceLogin, sourceHostname,
					mode);
		} else if (command.equals("TOPIC")) {
			// Someone is changing the topic.
			String[] args = line.split(" ");
			for (String s : args) {
				if (s.startsWith("#") || s.startsWith("&")) {
					target = s;
				}
			}
			this.onTopic(target, line.substring(line.indexOf(" :") + 2),
					sourceNick, sourceLogin, sourceHostname,
					System.currentTimeMillis(), true);
		} else if (command.equals("INVITE")) {
			// Somebody is inviting somebody else into a channel.
			this.onInvitation(target, sourceNick, sourceLogin, sourceHostname,
					line.substring(line.indexOf(" :") + 2));
		} else {
			// If we reach this point, then we've found something that the
			// PircBot
			// Doesn't currently deal with.
			this.onUnknown(line);
		}

	}

	/**
	 * Returns the hashCode of this PircBot. This method can be called by hashed
	 * collection classes and is useful for managing multiple instances of
	 * PircBots in such collections.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @return the hash code for this instance of PircBot.
	 */
	public int hashCode() {
		return super.hashCode();
	}

	/**
	 * Identify the bot with NickServ, supplying the appropriate password. Some
	 * IRC Networks (such as freenode) require users to <i>register</i> and
	 * <i>identify</i> with NickServ before they are able to send private
	 * messages to other users, thus reducing the amount of spam. If you are
	 * using an IRC network where this kind of policy is enforced, you will need
	 * to make your bot <i>identify</i> itself to NickServ before you can send
	 * private messages. Assuming you have already registered your bot's nick
	 * with NickServ, this method can be used to <i>identify</i> with the
	 * supplied password. It usually makes sense to identify with NickServ
	 * immediately after connecting to a server.
	 * <p>
	 * This method issues a raw NICKSERV command to the server, and is therefore
	 * safer than the alternative approach of sending a private message to
	 * NickServ. The latter approach is considered dangerous, as it may cause
	 * you to inadvertently transmit your password to an untrusted party if you
	 * connect to a network which does not run a NickServ service and where the
	 * untrusted party has assumed the nick "NickServ". However, if your IRC
	 * network is only compatible with the private message approach, you may
	 * typically identify like so:
	 * 
	 * <pre>
	 * sendMessage(&quot;NickServ&quot;, &quot;identify PASSWORD&quot;);
	 * </pre>
	 * 
	 * @param password
	 *            The password which will be used to identify with NickServ.
	 */
	public void identify(String password) {
		this.sendRawLine("NICKSERV IDENTIFY " + password);
	}

	/**
	 * A convenient method that accepts an IP address represented by a byte[] of
	 * size 4 and returns this as a long representation of the same IP address.
	 * 
	 * @since PircBot 0.9.4
	 * 
	 * @param address
	 *            the byte[] of size 4 representing the IP address.
	 * 
	 * @return a long representation of the IP address.
	 */
	public long ipToLong(byte[] address) {
		if (address.length != 4) {
			throw new IllegalArgumentException("byte array must be of length 4");
		}
		long ipNum = 0;
		long multiplier = 1;
		for (int i = 3; i >= 0; i--) {
			int byteVal = (address[i] + 256) % 256;
			ipNum += byteVal * multiplier;
			multiplier *= 256;
		}
		return ipNum;
	}

	/**
	 * Returns whether or not the PircBot is currently connected to a server.
	 * The result of this method should only act as a rough guide, as the result
	 * may not be valid by the time you act upon it.
	 * 
	 * @return True if and only if the PircBot is currently connected to a
	 *         server.
	 */
	public final synchronized boolean isConnected() {
		return _inputThread != null && _inputThread.isConnected();
	}

	private boolean isUserMode(char atPos) {
		try {
			return UserMode.valueOf("" + atPos) != null;
		} catch (Exception e) {
			return false;
		}
	}

	/**
	 * Joins a channel.
	 * 
	 * @param channel
	 *            The name of the channel to join (eg "#cs").
	 */
	public void joinChannel(String channel) {
		this.sendRawLine("JOIN " + channel);
	}

	/**
	 * Joins a channel with a key.
	 * 
	 * @param channel
	 *            The name of the channel to join (eg "#cs").
	 * @param key
	 *            The key that will be used to join the channel.
	 */
	public void joinChannel(String channel, String key) {
		this.joinChannel(channel + " " + key);
	}

	/**
	 * Kicks a user from a channel. This method attempts to kick a user from a
	 * channel and may require the bot to have operator status in the channel.
	 * 
	 * @param channel
	 *            The channel to kick the user from.
	 * @param nick
	 *            The nick of the user to kick.
	 */
	public void kick(String channel, String nick) {
		this.kick(channel, nick, "");
	}

	/**
	 * Kicks a user from a channel, giving a reason. This method attempts to
	 * kick a user from a channel and may require the bot to have operator
	 * status in the channel.
	 * 
	 * @param channel
	 *            The channel to kick the user from.
	 * @param nick
	 *            The nick of the user to kick.
	 * @param reason
	 *            A description of the reason for kicking a user.
	 */
	public void kick(String channel, String nick, String reason) {
		this.sendRawLine("KICK " + channel + " " + nick + " :" + reason);
	}

	/**
	 * Issues a request for a list of all channels on the IRC server. When the
	 * PircBot receives information for each channel, it will call the
	 * onChannelInfo method, which you will need to override if you want it to
	 * do anything useful.
	 * 
	 * @see #onChannelInfo(String,int,String) onChannelInfo
	 */
	public void listChannels() {
		this.listChannels(null);
	}

	/**
	 * Issues a request for a list of all channels on the IRC server. When the
	 * PircBot receives information for each channel, it will call the
	 * onChannelInfo method, which you will need to override if you want it to
	 * do anything useful.
	 * <p>
	 * Some IRC servers support certain parameters for LIST requests. One
	 * example is a parameter of ">10" to list only those channels that have
	 * more than 10 users in them. Whether these parameters are supported or not
	 * will depend on the IRC server software.
	 * 
	 * @param parameters
	 *            The parameters to supply when requesting the list.
	 * 
	 * @see #onChannelInfo(String,int,String) onChannelInfo
	 */
	public void listChannels(String parameters) {
		if (parameters == null) {
			this.sendRawLine("LIST");
		} else {
			this.sendRawLine("LIST " + parameters);
		}
	}

	/**
	 * Adds a line to the log. This log is currently output to the standard
	 * output and is in the correct format for use by tools such as pisg, the
	 * Perl IRC Statistics Generator. You may override this method if you wish
	 * to do something else with log entries. Each line in the log begins with a
	 * number which represents the logging time (as the number of milliseconds
	 * since the epoch). This timestamp and the following log entry are
	 * separated by a single space character, " ". Outgoing messages are
	 * distinguishable by a log entry that has ">>>" immediately following the
	 * space character after the timestamp. DCC events use "+++" and warnings
	 * about unhandled Exceptions and Errors use "###".
	 * <p>
	 * This implementation of the method will only cause log entries to be
	 * output if the PircBot has had its verbose mode turned on by calling
	 * setVerbose(true);
	 * 
	 * @param line
	 *            The line to add to the log.
	 */
	public void log(String line) {
		if (_verbose) {
			System.out.println(System.currentTimeMillis() + " " + line);
		}
	}

	/**
	 * A convenient method that accepts an IP address represented as a long and
	 * returns an integer array of size 4 representing the same IP address.
	 * 
	 * @since PircBot 0.9.4
	 * 
	 * @param address
	 *            the long value representing the IP address.
	 * 
	 * @return An int[] of size 4.
	 */
	public int[] longToIp(long address) {
		int[] ip = new int[4];
		for (int i = 3; i >= 0; i--) {
			ip[i] = (int) (address % 256);
			address = address / 256;
		}
		return ip;
	}

	/**
	 * This method is called whenever an ACTION is sent from a user. E.g. such
	 * events generated by typing "/me goes shopping" in most IRC clients.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param sender
	 *            The nick of the user that sent the action.
	 * @param login
	 *            The login of the user that sent the action.
	 * @param hostname
	 *            The hostname of the user that sent the action.
	 * @param target
	 *            The target of the action, be it a channel or our nick.
	 * @param action
	 *            The action carried out by the user.
	 */
	protected void onAction(String sender, String login, String hostname,
			String target, String action) {
	}

	protected void onAdmin(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String string) {

	}

	protected void onBeforeQuit(String sourceNick, String sourceLogin,
			String sourceHostname, String reason) {
	}

	/**
	 * After calling the listChannels() method in PircBot, the server will start
	 * to send us information about each channel on the server. You may override
	 * this method in order to receive the information about each channel as
	 * soon as it is received.
	 * <p>
	 * Note that certain channels, such as those marked as hidden, may not
	 * appear in channel listings.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The name of the channel.
	 * @param userCount
	 *            The number of users visible in this channel.
	 * @param topic
	 *            The topic for this channel.
	 * 
	 * @see #listChannels() listChannels
	 */
	protected void onChannelInfo(String channel, int userCount, String topic) {
	}

	/**
	 * This method is called once the PircBot has successfully connected to the
	 * IRC server.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.6
	 */
	protected void onConnect() {
	}

	/**
	 * This method used to be called when a DCC CHAT request was sent to the
	 * PircBot. Please use the onIncomingChatRequest method to accept chats, as
	 * it has better functionality.
	 * 
	 * @deprecated As of PircBot 1.2.0, use
	 *             {@link #onIncomingChatRequest(DccChat)}
	 */
	protected void onDccChatRequest(String sourceNick, String sourceLogin,
			String sourceHostname, long address, int port) {
	}

	/**
	 * This method used to be called when a DCC SEND request was sent to the
	 * PircBot. Please use the onIncomingFileTransfer method to receive files,
	 * as it has better functionality and supports resuming.
	 * 
	 * @deprecated As of PircBot 1.2.0, use
	 *             {@link #onIncomingFileTransfer(DccFileTransfer)}
	 */
	protected void onDccSendRequest(String sourceNick, String sourceLogin,
			String sourceHostname, String filename, long address, int port,
			int size) {
	}

	protected void onDeAdmin(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String string) {

	}

	/**
	 * Called when a user (possibly us) gets operator status taken away.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'deopped'.
	 */
	protected void onDeHalfop(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String recipient) {
	}

	/**
	 * Called when a user (possibly us) gets operator status taken away.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'deopped'.
	 */
	protected void onDeop(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String recipient) {
	}

	protected void onDeOwner(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String string) {

	}

	/**
	 * Called when a user (possibly us) gets voice status removed.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'devoiced'.
	 */
	protected void onDeVoice(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String recipient) {
	}

	/**
	 * This method carries out the actions to be performed when the PircBot gets
	 * disconnected. This may happen if the PircBot quits from the server, or if
	 * the connection is unexpectedly lost.
	 * <p>
	 * Disconnection from the IRC server is detected immediately if either we or
	 * the server close the connection normally. If the connection to the server
	 * is lost, but neither we nor the server have explicitly closed the
	 * connection, then it may take a few minutes to detect (this is commonly
	 * referred to as a "ping timeout").
	 * <p>
	 * If you wish to get your IRC bot to automatically rejoin a server after
	 * the connection has been lost, then this is probably the ideal method to
	 * override to implement such functionality.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 */
	protected void onDisconnect() {
	}

	/**
	 * This method gets called when a DccFileTransfer has finished. If there was
	 * a problem, the Exception will say what went wrong. If the file was sent
	 * successfully, the Exception will be null.
	 * <p>
	 * Both incoming and outgoing file transfers are passed to this method. You
	 * can determine the type by calling the isIncoming or isOutgoing methods on
	 * the DccFileTransfer object.
	 * 
	 * @since PircBot 1.2.0
	 * 
	 * @param transfer
	 *            The DccFileTransfer that has finished.
	 * @param e
	 *            null if the file was transfered successfully, otherwise this
	 *            will report what went wrong.
	 * 
	 * @see DccFileTransfer
	 * 
	 */
	protected void onFileTransferFinished(DccFileTransfer transfer, Exception e) {
	}

	/**
	 * This method is called whenever we receive a FINGER request.
	 * <p>
	 * This abstract implementation responds correctly, so if you override this
	 * method, be sure to either mimic its functionality or to call
	 * super.onFinger(...);
	 * 
	 * @param sourceNick
	 *            The nick of the user that sent the FINGER request.
	 * @param sourceLogin
	 *            The login of the user that sent the FINGER request.
	 * @param sourceHostname
	 *            The hostname of the user that sent the FINGER request.
	 * @param target
	 *            The target of the FINGER request, be it our nick or a channel
	 *            name.
	 */
	protected void onFinger(String sourceNick, String sourceLogin,
			String sourceHostname, String target) {
		this.sendRawLine("NOTICE " + sourceNick + " :\u0001FINGER " + _finger
				+ "\u0001");
	}

	/**
	 * Called when a user (possibly us) gets operator status taken away.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'deopped'.
	 */
	protected void onHalfop(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String recipient) {
	}

	/**
	 * This method will be called whenever a DCC Chat request is received. This
	 * means that a client has requested to chat to us directly rather than via
	 * the IRC server. This is useful for sending many lines of text to and from
	 * the bot without having to worry about flooding the server or any
	 * operators of the server being able to "spy" on what is being said. This
	 * abstract implementation performs no action, which means that all DCC CHAT
	 * requests will be ignored by default.
	 * <p>
	 * If you wish to accept the connection, then you may override this method
	 * and call the accept() method on the DccChat object, which connects to the
	 * sender of the chat request and allows lines to be sent to and from the
	 * bot.
	 * <p>
	 * Your bot must be able to connect directly to the user that sent the
	 * request.
	 * <p>
	 * Example:
	 * 
	 * <pre>
	 * public void onIncomingChatRequest(DccChat chat) {
	 * 	try {
	 * 		// Accept all chat, whoever it's from.
	 * 		chat.accept();
	 * 		chat.sendLine(&quot;Hello&quot;);
	 * 		String response = chat.readLine();
	 * 		chat.close();
	 * 	} catch (IOException e) {
	 * 	}
	 * }
	 * </pre>
	 * 
	 * Each time this method is called, it is called from within a new Thread so
	 * that multiple DCC CHAT sessions can run concurrently.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 1.2.0
	 * 
	 * @param chat
	 *            A DccChat object that represents the incoming chat request.
	 * 
	 * @see DccChat
	 * 
	 */
	protected void onIncomingChatRequest(DccChat chat) {
	}

	/**
	 * This method is called whenever a DCC SEND request is sent to the PircBot.
	 * This means that a client has requested to send a file to us. This
	 * abstract implementation performs no action, which means that all DCC SEND
	 * requests will be ignored by default. If you wish to receive the file,
	 * then you may override this method and call the receive method on the
	 * DccFileTransfer object, which connects to the sender and downloads the
	 * file.
	 * <p>
	 * Example:
	 * 
	 * <pre>
	 * public void onIncomingFileTransfer(DccFileTransfer transfer) {
	 * 	// Use the suggested file name.
	 * 	File file = transfer.getFile();
	 * 	// Receive the transfer and save it to the file, allowing resuming.
	 * 	transfer.receive(file, true);
	 * }
	 * </pre>
	 * <p>
	 * <b>Warning:</b> Receiving an incoming file transfer will cause a file to
	 * be written to disk. Please ensure that you make adequate security checks
	 * so that this file does not overwrite anything important!
	 * <p>
	 * Each time a file is received, it happens within a new Thread in order to
	 * allow multiple files to be downloaded by the PircBot at the same time.
	 * <p>
	 * If you allow resuming and the file already partly exists, it will be
	 * appended to instead of overwritten. If resuming is not enabled, the file
	 * will be overwritten if it already exists.
	 * <p>
	 * You can throttle the speed of the transfer by calling the setPacketDelay
	 * method on the DccFileTransfer object, either before you receive the file
	 * or at any moment during the transfer.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 1.2.0
	 * 
	 * @param transfer
	 *            The DcccFileTransfer that you may accept.
	 * 
	 * @see DccFileTransfer
	 * 
	 */
	protected void onIncomingFileTransfer(DccFileTransfer transfer) {
	}

	/**
	 * Called when we are invited to a channel by a user.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param targetNick
	 *            The nick of the user being invited - should be us!
	 * @param sourceNick
	 *            The nick of the user that sent the invitation.
	 * @param sourceLogin
	 *            The login of the user that sent the invitation.
	 * @param sourceHostname
	 *            The hostname of the user that sent the invitation.
	 * @param channel
	 *            The channel that we're being invited to.
	 */
	protected void onInvitation(String targetNick, String sourceNick,
			String sourceLogin, String sourceHostname, String channel) {
	}

	/**
	 * This method is called whenever someone (possibly us) joins a channel
	 * which we are on.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel which somebody joined.
	 * @param sender
	 *            The nick of the user who joined the channel.
	 * @param login
	 *            The login of the user who joined the channel.
	 * @param hostname
	 *            The hostname of the user who joined the channel.
	 */
	protected void onJoin(String channel, String sender, String login,
			String hostname) {
	}

	/**
	 * This method is called whenever someone (possibly us) is kicked from any
	 * of the channels that we are in.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel from which the recipient was kicked.
	 * @param kickerNick
	 *            The nick of the user who performed the kick.
	 * @param kickerLogin
	 *            The login of the user who performed the kick.
	 * @param kickerHostname
	 *            The hostname of the user who performed the kick.
	 * @param recipientNick
	 *            The unfortunate recipient of the kick.
	 * @param reason
	 *            The reason given by the user who performed the kick.
	 */
	protected void onKick(String channel, String kickerNick,
			String kickerLogin, String kickerHostname, String recipientNick,
			String reason) {
	}

	/**
	 * This method is called whenever a message is sent to a channel.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel to which the message was sent.
	 * @param sender
	 *            The nick of the person who sent the message.
	 * @param login
	 *            The login of the person who sent the message.
	 * @param hostname
	 *            The hostname of the person who sent the message.
	 * @param message
	 *            The actual message sent to the channel.
	 */
	protected void onMessage(String channel, String sender, String login,
			String hostname, String message) {
	}

	/**
	 * Called when the mode of a channel is set.
	 * <p>
	 * You may find it more convenient to decode the meaning of the mode string
	 * by overriding the onOp, onDeOp, onVoice, onDeVoice, onChannelKey,
	 * onDeChannelKey, onChannelLimit, onDeChannelLimit, onChannelBan or
	 * onDeChannelBan methods as appropriate.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel that the mode operation applies to.
	 * @param sourceNick
	 *            The nick of the user that set the mode.
	 * @param sourceLogin
	 *            The login of the user that set the mode.
	 * @param sourceHostname
	 *            The hostname of the user that set the mode.
	 * @param mode
	 *            The mode that has been set.
	 * 
	 */
	protected void onMode(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String mode) {
	}

	/**
	 * This method is called whenever someone (possibly us) changes nick on any
	 * of the channels that we are on.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param oldNick
	 *            The old nick.
	 * @param login
	 *            The login of the user.
	 * @param hostname
	 *            The hostname of the user.
	 * @param newNick
	 *            The new nick.
	 */
	protected void onNickChange(String oldNick, String login, String hostname,
			String newNick) {
	}

	/**
	 * This method is called whenever we receive a notice.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param sourceNick
	 *            The nick of the user that sent the notice.
	 * @param sourceLogin
	 *            The login of the user that sent the notice.
	 * @param sourceHostname
	 *            The hostname of the user that sent the notice.
	 * @param target
	 *            The target of the notice, be it our nick or a channel name.
	 * @param notice
	 *            The notice message.
	 */
	protected void onNotice(String sourceNick, String sourceLogin,
			String sourceHostname, String target, String notice) {
	}

	/**
	 * Called when a user (possibly us) gets granted operator status for a
	 * channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'opped'.
	 */
	protected void onOp(String channel, String sourceNick, String sourceLogin,
			String sourceHostname, String recipient) {
	}

	protected void onOwner(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String string) {

	}

	/**
	 * This method is called whenever someone (possibly us) parts a channel
	 * which we are on.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel which somebody parted from.
	 * @param sender
	 *            The nick of the user who parted from the channel.
	 * @param login
	 *            The login of the user who parted from the channel.
	 * @param hostname
	 *            The hostname of the user who parted from the channel.
	 */
	protected void onPart(String channel, String sender, String login,
			String hostname) {
	}

	/**
	 * This method is called whenever we receive a PING request from another
	 * user.
	 * <p>
	 * This abstract implementation responds correctly, so if you override this
	 * method, be sure to either mimic its functionality or to call
	 * super.onPing(...);
	 * 
	 * @param sourceNick
	 *            The nick of the user that sent the PING request.
	 * @param sourceLogin
	 *            The login of the user that sent the PING request.
	 * @param sourceHostname
	 *            The hostname of the user that sent the PING request.
	 * @param target
	 *            The target of the PING request, be it our nick or a channel
	 *            name.
	 * @param pingValue
	 *            The value that was supplied as an argument to the PING
	 *            command.
	 */
	protected void onPing(String sourceNick, String sourceLogin,
			String sourceHostname, String target, String pingValue) {
		this.sendRawLine("NOTICE " + sourceNick + " :\u0001PING " + pingValue
				+ "\u0001");
	}

	/**
	 * This method is called whenever a private message is sent to the PircBot.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param sender
	 *            The nick of the person who sent the private message.
	 * @param login
	 *            The login of the person who sent the private message.
	 * @param hostname
	 *            The hostname of the person who sent the private message.
	 * @param message
	 *            The actual message.
	 */
	protected void onPrivateMessage(String sender, String login,
			String hostname, String message) {
	}

	/**
	 * This method is called whenever someone (possibly us) quits from the
	 * server. We will only observe this if the user was in one of the channels
	 * to which we are connected.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param sourceNick
	 *            The nick of the user that quit from the server.
	 * @param sourceLogin
	 *            The login of the user that quit from the server.
	 * @param sourceHostname
	 *            The hostname of the user that quit from the server.
	 * @param reason
	 *            The reason given for quitting the server.
	 */
	protected void onQuit(String sourceNick, String sourceLogin,
			String sourceHostname, String reason) {
	}

	/**
	 * Called when a hostmask ban is removed from a channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param hostmask
	 */
	protected void onRemoveChannelBan(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String hostmask) {
	}

	/**
	 * Called when a channel key is removed.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param key
	 *            The key that was in use before the channel key was removed.
	 */
	protected void onRemoveChannelKey(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String key) {
	}

	/**
	 * Called when the user limit is removed for a channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveChannelLimit(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel has 'invite only' removed.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveInviteOnly(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel has moderated mode removed.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveModerated(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is set to allow messages from any user, even if
	 * they are not actually in the channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveNoExternalMessages(String channel,
			String sourceNick, String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is marked as not being in private mode.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemovePrivate(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel has 'secret' mode removed.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveSecret(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when topic protection is removed for a channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onRemoveTopicProtection(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * The actions to perform when a PING request comes from the server.
	 * <p>
	 * This sends back a correct response, so if you override this method, be
	 * sure to either mimic its functionality or to call
	 * super.onServerPing(response);
	 * 
	 * @param response
	 *            The response that should be given back in your PONG.
	 */
	protected void onServerPing(String response) {
		this.sendRawLine("PONG " + response);
	}

	/**
	 * This method is called when we receive a numeric response from the IRC
	 * server.
	 * <p>
	 * Numerics in the range from 001 to 099 are used for client-server
	 * connections only and should never travel between servers. Replies
	 * generated in response to commands are found in the range from 200 to 399.
	 * Error replies are found in the range from 400 to 599.
	 * <p>
	 * For example, we can use this method to discover the topic of a channel
	 * when we join it. If we join the channel #test which has a topic of
	 * &quot;I am King of Test&quot; then the response will be &quot;
	 * <code>PircBot #test :I Am King of Test</code> &quot; with a code of 332
	 * to signify that this is a topic. (This is just an example - note that
	 * overriding the <code>onTopic</code> method is an easier way of finding
	 * the topic for a channel). Check the IRC RFC for the full list of other
	 * command response codes.
	 * <p>
	 * PircBot implements the interface ReplyConstants, which contains
	 * contstants that you may find useful here.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param code
	 *            The three-digit numerical code for the response.
	 * @param response
	 *            The full response from the IRC server.
	 * 
	 * @see ReplyConstants
	 */
	protected void onServerResponse(int code, String response) {
	}

	/**
	 * Called when a user (possibly us) gets banned from a channel. Being banned
	 * from a channel prevents any user with a matching hostmask from joining
	 * the channel. For this reason, most bans are usually directly followed by
	 * the user being kicked :-)
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param hostmask
	 *            The hostmask of the user that has been banned.
	 */
	protected void onSetChannelBan(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String hostmask) {
	}

	/**
	 * Called when a channel key is set. When the channel key has been set,
	 * other users may only join that channel if they know the key. Channel keys
	 * are sometimes referred to as passwords.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param key
	 *            The new key for the channel.
	 */
	protected void onSetChannelKey(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String key) {
	}

	/**
	 * Called when a user limit is set for a channel. The number of users in the
	 * channel cannot exceed this limit.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param limit
	 *            The maximum number of users that may be in this channel at the
	 *            same time.
	 */
	protected void onSetChannelLimit(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, int limit) {
	}

	/**
	 * Called when a channel is set to 'invite only' mode. A user may only join
	 * the channel if they are invited by someone who is already in the channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetInviteOnly(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is set to 'moderated' mode. If a channel is
	 * moderated, then only users who have been 'voiced' or 'opped' may speak or
	 * change their nicks.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetModerated(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is set to only allow messages from users that are
	 * in the channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetNoExternalMessages(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is marked as being in private mode.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetPrivate(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when a channel is set to be in 'secret' mode. Such channels
	 * typically do not appear on a server's channel listing.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetSecret(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * Called when topic protection is enabled for a channel. Topic protection
	 * means that only operators in a channel may change the topic.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 */
	protected void onSetTopicProtection(String channel, String sourceNick,
			String sourceLogin, String sourceHostname) {
	}

	/**
	 * This method is called whenever we receive a TIME request.
	 * <p>
	 * This abstract implementation responds correctly, so if you override this
	 * method, be sure to either mimic its functionality or to call
	 * super.onTime(...);
	 * 
	 * @param sourceNick
	 *            The nick of the user that sent the TIME request.
	 * @param sourceLogin
	 *            The login of the user that sent the TIME request.
	 * @param sourceHostname
	 *            The hostname of the user that sent the TIME request.
	 * @param target
	 *            The target of the TIME request, be it our nick or a channel
	 *            name.
	 */
	protected void onTime(String sourceNick, String sourceLogin,
			String sourceHostname, String target) {
		this.sendRawLine("NOTICE " + sourceNick + " :\u0001TIME "
				+ new Date().toString() + "\u0001");
	}

	/**
	 * This method is called whenever a user sets the topic, or when PircBot
	 * joins a new channel and discovers its topic.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel that the topic belongs to.
	 * @param topic
	 *            The topic for the channel.
	 * 
	 * @deprecated As of 1.2.0, replaced by
	 *             {@link #onTopic(String,String,String,long,boolean)}
	 */
	protected void onTopic(String channel, String topic) {
	}

	/**
	 * This method is called whenever a user sets the topic, or when PircBot
	 * joins a new channel and discovers its topic.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param channel
	 *            The channel that the topic belongs to.
	 * @param topic
	 *            The topic for the channel.
	 * @param setBy
	 *            The nick of the user that set the topic.
	 * @param date
	 *            When the topic was set (milliseconds since the epoch).
	 * @param changed
	 *            True if the topic has just been changed, false if the topic
	 *            was already there.
	 * 
	 */
	protected void onTopic(String channel, String topic, String setBy,
			String setByLogin, String setByHostname, long date, boolean changed) {
	}

	/**
	 * This method is called whenever we receive a line from the server that the
	 * PircBot has not been programmed to recognise.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @param line
	 *            The raw line that was received from the server.
	 */
	protected void onUnknown(String line) {
		// And then there were none :)
	}

	/**
	 * This method is called when we receive a user list from the server after
	 * joining a channel.
	 * <p>
	 * Shortly after joining a channel, the IRC server sends a list of all users
	 * in that channel. The PircBot collects this information and calls this
	 * method as soon as it has the full list.
	 * <p>
	 * To obtain the nick of each user in the channel, call the getNick() method
	 * on each User object in the array.
	 * <p>
	 * At a later time, you may call the getUsers method to obtain an up to date
	 * list of the users in the channel.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 1.0.0
	 * 
	 * @param channel
	 *            The name of the channel.
	 * @param users
	 *            An array of User objects belonging to this channel.
	 * 
	 * @see User
	 */
	protected void onUserList(String channel, IrcUser[] users) {
	}

	/**
	 * Called when the mode of a user is set.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 1.2.0
	 * 
	 * @param targetNick
	 *            The nick that the mode operation applies to.
	 * @param sourceNick
	 *            The nick of the user that set the mode.
	 * @param sourceLogin
	 *            The login of the user that set the mode.
	 * @param sourceHostname
	 *            The hostname of the user that set the mode.
	 * @param mode
	 *            The mode that has been set.
	 * 
	 */
	protected void onUserMode(String targetNick, String sourceNick,
			String sourceLogin, String sourceHostname, String mode) {
	}

	/**
	 * This method is called whenever we receive a VERSION request. This
	 * abstract implementation responds with the PircBot's _version string, so
	 * if you override this method, be sure to either mimic its functionality or
	 * to call super.onVersion(...);
	 * 
	 * @param sourceNick
	 *            The nick of the user that sent the VERSION request.
	 * @param sourceLogin
	 *            The login of the user that sent the VERSION request.
	 * @param sourceHostname
	 *            The hostname of the user that sent the VERSION request.
	 * @param target
	 *            The target of the VERSION request, be it our nick or a channel
	 *            name.
	 */
	protected void onVersion(String sourceNick, String sourceLogin,
			String sourceHostname, String target) {
		this.sendRawLine("NOTICE " + sourceNick + " :\u0001VERSION " + _version
				+ "\u0001");
	}

	/**
	 * Called when a user (possibly us) gets voice status granted in a channel.
	 * <p>
	 * This is a type of mode change and is also passed to the onMode method in
	 * the PircBot class.
	 * <p>
	 * The implementation of this method in the PircBot abstract class performs
	 * no actions and may be overridden as required.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param channel
	 *            The channel in which the mode change took place.
	 * @param sourceNick
	 *            The nick of the user that performed the mode change.
	 * @param sourceLogin
	 *            The login of the user that performed the mode change.
	 * @param sourceHostname
	 *            The hostname of the user that performed the mode change.
	 * @param recipient
	 *            The nick of the user that got 'voiced'.
	 */
	protected void onVoice(String channel, String sourceNick,
			String sourceLogin, String sourceHostname, String recipient) {
	}

	/**
	 * Grants operator privilidges to a user on a channel. Successful use of
	 * this method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're opping the user on.
	 * @param nick
	 *            The nick of the user we are opping.
	 */
	public void op(String channel, String nick) {
		this.setMode(channel, "+o " + nick);
	}

	/**
	 * Parts a channel.
	 * 
	 * @param channel
	 *            The name of the channel to leave.
	 */
	public void partChannel(String channel) {
		this.sendRawLine("PART " + channel);
	}

	/**
	 * Parts a channel, giving a reason.
	 * 
	 * @param channel
	 *            The name of the channel to leave.
	 * @param reason
	 *            The reason for parting the channel.
	 */
	public void partChannel(String channel, String reason) {
		this.sendRawLine("PART " + channel + " :" + reason);
	}

	/**
	 * Called when the mode of a channel is set. We process this in order to
	 * call the appropriate onOp, onDeop, etc method before finally calling the
	 * override-able onMode method.
	 * <p>
	 * Note that this method is private and is not intended to appear in the
	 * javadoc generated documentation.
	 * 
	 * @param target
	 *            The channel or nick that the mode operation applies to.
	 * @param sourceNick
	 *            The nick of the user that set the mode.
	 * @param sourceLogin
	 *            The login of the user that set the mode.
	 * @param sourceHostname
	 *            The hostname of the user that set the mode.
	 * @param mode
	 *            The mode that has been set.
	 */
	private final void processMode(String target, String sourceNick,
			String sourceLogin, String sourceHostname, String mode) {

		if (_channelPrefixes.indexOf(target.charAt(0)) >= 0) {
			// The mode of a channel is being changed.
			String channel = target;
			StringTokenizer tok = new StringTokenizer(mode);
			String[] params = new String[tok.countTokens()];

			int t = 0;
			while (tok.hasMoreTokens()) {
				params[t] = tok.nextToken();
				t++;
			}

			char pn = ' ';
			int p = 1;

			// All of this is very large and ugly, but it's the only way of
			// providing
			// what the users want :-/
			for (int i = 0; i < params[0].length(); i++) {
				char atPos = params[0].charAt(i);

				if (atPos == '+' || atPos == '-') {
					pn = atPos;
				} else if (isUserMode(atPos)) {
					/*
					 * We'll update the user first
					 */
					this.updateUser(channel, UserMode.valueOf("" + atPos)
							.getSymbol(), pn == '+', params[p]);
					/*
					 * Then we'll call the listener method if there is one
					 */
					if (atPos == 'o') {
						if (pn == '+') {
							onOp(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						} else {
							onDeop(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						}
					} else if (atPos == 'v') {
						if (pn == '+') {
							onVoice(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						} else {
							onDeVoice(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						}
					} else if (atPos == 'h') {
						if (pn == '+') {
							onHalfop(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						} else {
							onDeHalfop(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						}
					} else if (atPos == 'a') {
						if (pn == '+') {
							onAdmin(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						} else {
							onDeAdmin(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						}
					} else if (atPos == 'q') {
						if (pn == '+') {
							onOwner(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						} else {
							onDeOwner(channel, sourceNick, sourceLogin,
									sourceHostname, params[p]);
						}
					}
					/*
					 * Then we'll increment the parameter positions
					 */
					p++;
				} else if (atPos == 'k') {
					if (pn == '+') {
						onSetChannelKey(channel, sourceNick, sourceLogin,
								sourceHostname, params[p]);
					} else {
						onRemoveChannelKey(channel, sourceNick, sourceLogin,
								sourceHostname, params[p]);
					}
					p++;
				} else if (atPos == 'l') {
					if (pn == '+') {
						onSetChannelLimit(channel, sourceNick, sourceLogin,
								sourceHostname, Integer.parseInt(params[p]));
						p++;
					} else {
						onRemoveChannelLimit(channel, sourceNick, sourceLogin,
								sourceHostname);
					}
				} else if (atPos == 'b') {
					if (pn == '+') {
						onSetChannelBan(channel, sourceNick, sourceLogin,
								sourceHostname, params[p]);
					} else {
						onRemoveChannelBan(channel, sourceNick, sourceLogin,
								sourceHostname, params[p]);
					}
					p++;
				} else if (atPos == 't') {
					if (pn == '+') {
						onSetTopicProtection(channel, sourceNick, sourceLogin,
								sourceHostname);
					} else {
						onRemoveTopicProtection(channel, sourceNick,
								sourceLogin, sourceHostname);
					}
				} else if (atPos == 'n') {
					if (pn == '+') {
						onSetNoExternalMessages(channel, sourceNick,
								sourceLogin, sourceHostname);
					} else {
						onRemoveNoExternalMessages(channel, sourceNick,
								sourceLogin, sourceHostname);
					}
				} else if (atPos == 'i') {
					if (pn == '+') {
						onSetInviteOnly(channel, sourceNick, sourceLogin,
								sourceHostname);
					} else {
						onRemoveInviteOnly(channel, sourceNick, sourceLogin,
								sourceHostname);
					}
				} else if (atPos == 'm') {
					if (pn == '+') {
						onSetModerated(channel, sourceNick, sourceLogin,
								sourceHostname);
					} else {
						onRemoveModerated(channel, sourceNick, sourceLogin,
								sourceHostname);
					}
				} else if (atPos == 'p') {
					if (pn == '+') {
						onSetPrivate(channel, sourceNick, sourceLogin,
								sourceHostname);
					} else {
						onRemovePrivate(channel, sourceNick, sourceLogin,
								sourceHostname);
					}
				} else if (atPos == 's') {
					if (pn == '+') {
						onSetSecret(channel, sourceNick, sourceLogin,
								sourceHostname);
					} else {
						onRemoveSecret(channel, sourceNick, sourceLogin,
								sourceHostname);
					}
				}
			}

			this.onMode(channel, sourceNick, sourceLogin, sourceHostname, mode);
		} else {
			// The mode of a user is being changed.
			String nick = target;
			this.onUserMode(nick, sourceNick, sourceLogin, sourceHostname, mode);
		}
	}

	/**
	 * This method is called by the PircBot when a numeric response is received
	 * from the IRC server. We use this method to allow PircBot to process
	 * various responses from the server before then passing them on to the
	 * onServerResponse method.
	 * <p>
	 * Note that this method is private and should not appear in any of the
	 * javadoc generated documenation.
	 * 
	 * @param code
	 *            The three-digit numerical code for the response.
	 * @param response
	 *            The full response from the IRC server.
	 */
	private final void processServerResponse(int code, String response) {

		if (code == RPL_LIST) {
			// This is a bit of information about a channel.
			int firstSpace = response.indexOf(' ');
			int secondSpace = response.indexOf(' ', firstSpace + 1);
			int thirdSpace = response.indexOf(' ', secondSpace + 1);
			int colon = response.indexOf(':');
			String channel = response.substring(firstSpace + 1, secondSpace);
			int userCount = 0;
			try {
				userCount = Integer.parseInt(response.substring(
						secondSpace + 1, thirdSpace));
			} catch (NumberFormatException e) {
				// Stick with the value of zero.
			}
			String topic = response.substring(colon + 1);
			this.onChannelInfo(channel, userCount, topic);
		} else if (code == RPL_TOPIC) {
			// This is topic information about a channel we've just joined.
			int firstSpace = response.indexOf(' ');
			int secondSpace = response.indexOf(' ', firstSpace + 1);
			int colon = response.indexOf(':');
			String channel = response.substring(firstSpace + 1, secondSpace);
			String topic = response.substring(colon + 1);

			_topics.put(channel, topic);

			// For backwards compatibility only - this onTopic method is
			// deprecated.
			this.onTopic(channel, topic);
		} else if (code == RPL_TOPICINFO) {
			StringTokenizer tokenizer = new StringTokenizer(response);
			tokenizer.nextToken();
			String channel = tokenizer.nextToken();
			String setBy = tokenizer.nextToken();
			long date = 0;
			try {
				date = Long.parseLong(tokenizer.nextToken()) * 1000;
			} catch (NumberFormatException e) {
				// Stick with the default value of zero.
			}

			String topic = (String) _topics.get(channel);
			_topics.remove(channel);

			this.onTopic(channel, topic, setBy, "", "", date, false);
		} else if (code == RPL_NAMREPLY) {
			// This is a list of nicks in a channel that we've just joined.
			int channelEndIndex = response.indexOf(" :");
			String channel = response.substring(
					response.lastIndexOf(' ', channelEndIndex - 1) + 1,
					channelEndIndex);

			StringTokenizer tokenizer = new StringTokenizer(
					response.substring(response.indexOf(" :") + 2));
			while (tokenizer.hasMoreTokens()) {
				String nick = tokenizer.nextToken();
				String prefix = "";
				while (!nick.equals("")) {
					String c = null;
					if (nick.startsWith("@")) {
						// User is an operator in this channel.
						c = "@";
					} else if (nick.startsWith("+")) {
						// User is voiced in this channel.
						c = "+";
					} else if (nick.startsWith(".")) {
						// Some wibbly status I've never seen before...
						c = ".";
					} else if (nick.startsWith("~")) {
						c = "~";
					} else if (nick.startsWith("%")) {
						c = "%";
					} else if (nick.startsWith("&")) {
						c = "&";
					} else {
						break;
					}
					nick = nick.substring(prefix.length());
					prefix += c;
				}
				this.addUser(channel, new IrcUser(prefix, nick));
			}
		} else if (code == RPL_ENDOFNAMES) {
			// This is the end of a NAMES list, so we know that we've got
			// the full list of users in the channel that we just joined.
			String channel = response.substring(response.indexOf(' ') + 1,
					response.indexOf(" :"));
			IrcUser[] users = this.getUsers(channel);
			this.onUserList(channel, users);
		}

		this.onServerResponse(code, response);
	}

	/**
	 * Quits from the IRC server. Providing we are actually connected to an IRC
	 * server, the onDisconnect() method will be called as soon as the IRC
	 * server disconnects us.
	 */
	public void quitServer() {
		this.quitServer("");
	}

	/**
	 * Quits from the IRC server with a reason. Providing we are actually
	 * connected to an IRC server, the onDisconnect() method will be called as
	 * soon as the IRC server disconnects us.
	 * 
	 * @param reason
	 *            The reason for quitting the server.
	 */
	public void quitServer(String reason) {
		this.sendRawLine("QUIT :" + reason);
	}

	/**
	 * Reconnects to the IRC server that we were previously connected to. If
	 * necessary, the appropriate port number and password will be used. This
	 * method will throw an IrcException if we have never connected to an IRC
	 * server previously.
	 * 
	 * @since PircBot 0.9.9
	 * 
	 * @throws IOException
	 *             if it was not possible to connect to the server.
	 * @throws IrcException
	 *             if the server would not let us join it.
	 * @throws NickAlreadyInUseException
	 *             if our nick is already in use on the server.
	 */
	public final synchronized void reconnect() throws IOException,
			IrcException, NickAlreadyInUseException {
		if (getServer() == null) {
			throw new IrcException(
					"Cannot reconnect to an IRC server because we were never connected to one previously!");
		}
		// connect(getServer(), getPort(), getPassword());
		connect(getServer(), getPort(), getPassword(), getSocketFactory());
	}

	/**
	 * Removes all channels from our memory of users.
	 */
	private final void removeAllChannels() {
		_channels = new HashMap<String, Set<IrcUser>>();
	}

	/**
	 * Removes an entire channel from our memory of users.
	 */
	private final void removeChannel(String channel) {
		channel = channel.toLowerCase();
		synchronized (_channels) {
			_channels.remove(channel);
		}
	}

	/**
	 * Remove a user from all channels in our memory.
	 */
	private final void removeUser(String nick) {
		for (String channel : _channels.keySet()) {
			this.removeUser(channel, nick);
		}
	}

	/**
	 * Remove a user from the specified channel in our memory.
	 */
	protected IrcUser removeUser(String channel, String nick) {
		channel = channel.toLowerCase();
		IrcUser user = new IrcUser("", nick);
		Set<IrcUser> users = _channels.get(channel);
		if (users != null) {
			return users.remove(user) ? user : null;
		}
		return null;
	}

	/**
	 * Rename a user if they appear in any of the channels we know about.
	 */
	protected void renameUser(String oldNick, String newNick) {
		for (String channel : _channels.keySet()) {
			IrcUser user = this.removeUser(channel, oldNick);
			if (user != null) {
				user = new IrcUser(user.getPrefix(), newNick);
				this.addUser(channel, user);
			}
		}
	}

	/**
	 * Sends an action to the channel or to a user.
	 * 
	 * @param target
	 *            The name of the channel or user nick to send to.
	 * @param action
	 *            The action to send.
	 * 
	 * @see Colors
	 */
	public void sendAction(String target, String action) {
		target = target.replace("\n", "");
		action = action.replace("\n", "");
		sendCTCPCommand(target, "ACTION " + action);
	}

	/**
	 * Sends a CTCP command to a channel or user. (Client to client protocol).
	 * Examples of such commands are "PING <number>", "FINGER", "VERSION", etc.
	 * For example, if you wish to request the version of a user called "Dave",
	 * then you would call <code>sendCTCPCommand("Dave", "VERSION");</code>. The
	 * type of response to such commands is largely dependant on the target
	 * client software.
	 * 
	 * @since PircBot 0.9.5
	 * 
	 * @param target
	 *            The name of the channel or user to send the CTCP message to.
	 * @param command
	 *            The CTCP command to send.
	 */
	public void sendCTCPCommand(String target, String command) {
		_outQueue.add("PRIVMSG " + target + " :\u0001" + command + "\u0001");
	}

	/**
	 * Sends an invitation to join a channel. Some channels can be marked as
	 * "invite-only", so it may be useful to allow a bot to invite people into
	 * it.
	 * 
	 * @param nick
	 *            The nick of the user to invite
	 * @param channel
	 *            The channel you are inviting the user to join.
	 * 
	 */
	public void sendInvite(String nick, String channel) {
		this.sendRawLine("INVITE " + nick + " :" + channel);
	}

	/**
	 * Sends a message to a channel or a private message to a user. These
	 * messages are added to the outgoing message queue and sent at the earliest
	 * possible opportunity.
	 * <p>
	 * Some examples: -
	 * 
	 * <pre>
	 * // Send the message &quot;Hello!&quot; to the channel #cs.
	 * sendMessage(&quot;#cs&quot;, &quot;Hello!&quot;);
	 * 
	 * // Send a private message to Paul that says &quot;Hi&quot;.
	 * sendMessage(&quot;Paul&quot;, &quot;Hi&quot;);
	 * </pre>
	 * 
	 * You may optionally apply colours, boldness, underlining, etc to the
	 * message by using the <code>Colors</code> class.
	 * 
	 * @param target
	 *            The name of the channel or user nick to send to.
	 * @param message
	 *            The message to send.
	 * 
	 * @see Colors
	 */
	public void sendMessage(String target, String message) {
		target = target.replace("\n", "");
		message = message.replace("\n", "");
		_outQueue.add("PRIVMSG " + target + " :" + message);
	}

	/**
	 * Sends a notice to the channel or to a user.
	 * 
	 * @param target
	 *            The name of the channel or user nick to send to.
	 * @param notice
	 *            The notice to send.
	 */
	public void sendNotice(String target, String notice) {
		_outQueue.add("NOTICE " + target + " :" + notice);
	}

	/**
	 * Sends a raw line to the IRC server as soon as possible, bypassing the
	 * outgoing message queue.
	 * 
	 * @param line
	 *            The raw line to send to the IRC server.
	 */
	public synchronized void sendRawLine(String line) {
		if (isConnected()) {
			_inputThread.sendRawLine(line);
		}
	}

	/**
	 * Sends a raw line through the outgoing message queue.
	 * 
	 * @param line
	 *            The raw line to send to the IRC server.
	 */
	public synchronized void sendRawLineViaQueue(String line) {
		if (line == null) {
			throw new NullPointerException(
					"Cannot send null messages to server");
		}
		if (isConnected()) {
			_outQueue.add(line);
		}
	}

	/**
	 * When you connect to a server and your nick is already in use and this is
	 * set to true, a new nick will be automatically chosen. This is done by
	 * adding numbers to the end of the nick until an available nick is found.
	 * 
	 * @param autoNickChange
	 *            Set to true if you want automatic nick changes during
	 *            connection.
	 */
	public void setAutoNickChange(boolean autoNickChange) {
		_autoNickChange = autoNickChange;
	}

	/**
	 * Sets the InetAddress to be used when sending DCC chat or file transfers.
	 * This can be very useful when you are running a bot on a machine which is
	 * behind a firewall and you need to tell receiving clients to connect to a
	 * NAT/router, which then forwards the connection.
	 * 
	 * @since PircBot 1.4.4
	 * 
	 * @param dccInetAddress
	 *            The new InetAddress, or null to use the default.
	 */
	public void setDccInetAddress(InetAddress dccInetAddress) {
		_dccInetAddress = dccInetAddress;
	}

	/**
	 * Sets the choice of port numbers that can be used when sending a DCC chat
	 * or file transfer. This is useful when you are behind a firewall and need
	 * to set up port forwarding. The array of port numbers is traversed in
	 * sequence until a free port is found to listen on. A DCC tranfer will fail
	 * if all ports are already in use. If set to null, <i>any</i> free port
	 * number will be used.
	 * 
	 * @since PircBot 1.4.4
	 * 
	 * @param ports
	 *            The set of port numbers that PircBot may use for DCC
	 *            transfers, or null to let it use any free port (default).
	 * 
	 */
	public void setDccPorts(int[] ports) {
		if (ports == null || ports.length == 0) {
			_dccPorts = null;
		} else {
			// Clone the array to prevent external modification.
			_dccPorts = (int[]) ports.clone();
		}
	}

	/**
	 * Sets the encoding charset to be used when sending or receiving lines from
	 * the IRC server. If set to null, then the platform's default charset is
	 * used. You should only use this method if you are trying to send text to
	 * an IRC server in a different charset, e.g. "GB2312" for Chinese encoding.
	 * If a PircBot is currently connected to a server, then it must reconnect
	 * before this change takes effect.
	 * 
	 * @since PircBot 1.0.4
	 * 
	 * @param charset
	 *            The new encoding charset to be used by PircBot.
	 * 
	 * @throws UnsupportedEncodingException
	 *             If the named charset is not supported.
	 */
	public void setEncoding(String charset) throws UnsupportedEncodingException {
		// Just try to see if the charset is supported first...
		"".getBytes(charset);

		_charset = charset;
	}

	/**
	 * Sets the interal finger message. This should be set before joining any
	 * servers.
	 * 
	 * @param finger
	 *            The new finger message for the Bot.
	 */
	public final void setFinger(String finger) {
		_finger = finger;
	}

	/**
	 * Sets the internal login of the Bot. This should be set before joining any
	 * servers.
	 * 
	 * @param login
	 *            The new login of the Bot.
	 */
	public final void setLogin(String login) {
		_login = login;
	}

	/**
	 * Sets the number of milliseconds to delay between consecutive messages
	 * when there are multiple messages waiting in the outgoing message queue.
	 * This has a default value of 1000ms. It is a good idea to stick to this
	 * default value, as it will prevent your bot from spamming servers and
	 * facing the subsequent wrath! However, if you do need to change this delay
	 * value (<b>not recommended</b>), then this is the method to use.
	 * 
	 * @param delay
	 *            The number of milliseconds between each outgoing message.
	 * 
	 */
	public final void setMessageDelay(long delay) {
		if (delay < 0) {
			throw new IllegalArgumentException("Cannot have a negative time.");
		}
		_messageDelay = delay;
	}

	/**
	 * Set the mode of a channel. This method attempts to set the mode of a
	 * channel. This may require the bot to have operator status on the channel.
	 * For example, if the bot has operator status, we can grant operator status
	 * to "Dave" on the #cs channel by calling setMode("#cs", "+o Dave"); An
	 * alternative way of doing this would be to use the op method.
	 * 
	 * @param channel
	 *            The channel on which to perform the mode change.
	 * @param mode
	 *            The new mode to apply to the channel. This may include zero or
	 *            more arguments if necessary.
	 * 
	 * @see #op(String,String) op
	 */
	public void setMode(String channel, String mode) {
		this.sendRawLine("MODE " + channel + " " + mode);
	}

	/**
	 * Sets the name of the bot, which will be used as its nick when it tries to
	 * join an IRC server. This should be set before joining any servers,
	 * otherwise the default nick will be used. You would typically call this
	 * method from the constructor of the class that extends PircBot.
	 * <p>
	 * The changeNick method should be used if you wish to change your nick when
	 * you are connected to a server.
	 * 
	 * @param name
	 *            The new name of the Bot.
	 */
	public void setName(String name) {
		_name = name;
	}

	/**
	 * Sets the internal nick of the bot. This is only to be called by the
	 * PircBot class in response to notification of nick changes that apply to
	 * us.
	 * 
	 * @param nick
	 *            The new nick.
	 */
	private final void setNick(String nick) {
		_nick = nick;
	}

	/**
	 * Set the topic for a channel. This method attempts to set the topic of a
	 * channel. This may require the bot to have operator status if the topic is
	 * protected.
	 * 
	 * @param channel
	 *            The channel on which to perform the mode change.
	 * @param topic
	 *            The new topic for the channel.
	 * 
	 */
	public void setTopic(String channel, String topic) {
		this.sendRawLine("TOPIC " + channel + " :" + topic);
	}

	/**
	 * Sets the verbose mode. If verbose mode is set to true, then log entries
	 * will be printed to the standard output. The default value is false and
	 * will result in no output. For general development, we strongly recommend
	 * setting the verbose mode to true.
	 * 
	 * @param verbose
	 *            true if verbose mode is to be used. Default is false.
	 */
	public final void setVerbose(boolean verbose) {
		_verbose = verbose;
	}

	/**
	 * Sets the internal version of the Bot. This should be set before joining
	 * any servers.
	 * 
	 * @param version
	 *            The new version of the Bot.
	 */
	public final void setVersion(String version) {
		_version = version;
	}

	/**
	 * Starts an ident server (Identification Protocol Server, RFC 1413).
	 * <p>
	 * Most IRC servers attempt to contact the ident server on connecting hosts
	 * in order to determine the user's identity. A few IRC servers will not
	 * allow you to connect unless this information is provided.
	 * <p>
	 * So when a PircBot is run on a machine that does not run an ident server,
	 * it may be necessary to call this method to start one up.
	 * <p>
	 * Calling this method starts up an ident server which will respond with the
	 * login provided by calling getLogin() and then shut down immediately. It
	 * will also be shut down if it has not been contacted within 60 seconds of
	 * creation.
	 * <p>
	 * If you require an ident response, then the correct procedure is to start
	 * the ident server and then connect to the IRC server. The IRC server may
	 * then contact the ident server to get the information it needs.
	 * <p>
	 * The ident server will fail to start if there is already an ident server
	 * running on port 113, or if you are running as an unprivileged user who is
	 * unable to create a server socket on that port number.
	 * <p>
	 * If it is essential for you to use an ident server when connecting to an
	 * IRC server, then make sure that port 113 on your machine is visible to
	 * the IRC server so that it may contact the ident server.
	 * 
	 * @since PircBot 0.9c
	 */
	public final void startIdentServer() {
		new IdentServer(this, getLogin());
	}

	/**
	 * Returns a String representation of this object. You may find this useful
	 * for debugging purposes, particularly if you are using more than one
	 * PircBot instance to achieve multiple server connectivity. The format of
	 * this String may change between different versions of PircBot but is
	 * currently something of the form <code>
	 *   Version{PircBot x.y.z Java IRC Bot - www.jibble.org}
	 *   Connected{true}
	 *   Server{irc.dal.net}
	 *   Port{6667}
	 *   Password{}
	 * </code>
	 * 
	 * @since PircBot 0.9.10
	 * 
	 * @return a String representation of this object.
	 */
	public String toString() {
		return "Version{" + _version + "}" + " Connected{" + isConnected()
				+ "}" + " Server{" + _server + "}" + " Port{" + _port + "}"
				+ " Password{" + _password + "}";
	}

	/**
	 * Unbans a user from a channel. An example of a valid hostmask is
	 * "*!*compu@*.18hp.net". Successful use of this method may require the bot
	 * to have operator status itself.
	 * 
	 * @param channel
	 *            The channel to unban the user from.
	 * @param hostmask
	 *            A hostmask representing the user we're unbanning.
	 */
	public void unBan(String channel, String hostmask) {
		this.sendRawLine("MODE " + channel + " -b " + hostmask);
	}

	@SuppressWarnings("null")
	protected void updateUser(String channel, String prefix, boolean add,
			String nick) {
		channel = channel.toLowerCase();
		synchronized (_channels) {
			Set<IrcUser> users = (Set<IrcUser>) _channels.get(channel);
			IrcUser newUser = null;
			if (users != null) {
				for (IrcUser u : users) {
					if (u.getNick().equalsIgnoreCase(nick)) {
						newUser = new IrcUser(modifyPrefix(u.getPrefix(),
								prefix, add), nick);
					}
				}
			}
			if (newUser != null) {
				users.add(newUser);
			} else {
				// just in case ...
				newUser = new IrcUser("", nick);
				users.add(newUser);
			}
		}
	}

	/**
	 * Grants voice privilidges to a user on a channel. Successful use of this
	 * method may require the bot to have operator status itself.
	 * 
	 * @param channel
	 *            The channel we're voicing the user on.
	 * @param nick
	 *            The nick of the user we are voicing.
	 */
	public void voice(String channel, String nick) {
		this.setMode(channel, "+v " + nick);
	}

}
